package zhyioo.testframe;

import android.bluetooth.BluetoothAdapter;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.TextView;

import com.hardware.cipherdriver.CipherDriver;
import com.hardware.h003.HardwareDriver;
import com.lc.command.ICommandDriver;
import com.lc.driver.BluetoothTransmitHandler;
import com.lc.driver.HidFixedTransmitHandler;
import com.lc.driver.HidTransmitHandler;
import com.lc.driver.IOppBack;
import com.lc.driver.JniComTransmitHandler;
import com.lc.protocol.IBaseDevice;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class MainActivity extends AppCompatActivity implements View.OnClickListener, AdapterView.OnItemClickListener {

    protected int TextLogger = Color.rgb(0x80, 0x80, 0x80);
    protected int TextNote = Color.rgb(0x59, 0x59, 0x59);
    protected int TextMessage = Color.rgb(0x7E, 0xC2, 0xFF);
    protected int TextTips = Color.rgb(0xFF, 0x80, 0xC0);
    protected int TextInfo = Color.rgb(0x00, 0x82, 0xFF);
    protected int TextResult = Color.rgb(0x00, 0x00, 0xFF);
    protected int TextWarning = Color.rgb(0xD7, 0xAB, 0x69);
    protected int TextHighWarning = Color.rgb(0xBD, 0x63, 0xC5);
    protected int TextError = Color.rgb(0xFF, 0x00, 0x00);

    private TextView tv_log = null;
    private Button btn_clean = null;
    private Button btn_shot = null;
    private ScrollView sv_log = null;
    private ListView lv_test = null;
    private Class<?> mClassModule = null;
    private String[] tests = null;
    private Object mObject = null;

    private HardwareDriver _driver = null;
    private BluetoothTransmitHandler _bth = null;
    private JniComTransmitHandler _com3 = null;
    private HidTransmitHandler _hiddev = null;
    private HidFixedTransmitHandler _hid = null;
    private IBaseDevice _basedev = null;
    private CipherDriver _cipher = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        try {
            mClassModule = Class.forName(MainActivity.class.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }

        mObject = this;
        tv_log = (TextView) findViewById(R.id.tv_log);
        btn_clean = (Button) findViewById(R.id.btn_clean);
        btn_shot = (Button) findViewById(R.id.btn_shot);
        sv_log = (ScrollView) findViewById(R.id.sv_log);
        lv_test = (ListView) findViewById(R.id.lv_test);

        btn_clean.setOnClickListener(this);
        btn_shot.setOnClickListener(this);
        lv_test.setOnItemClickListener(this);

        String[] itemArray = getResources().getStringArray(R.array.Tests);
        String[] titleArray = new String[itemArray.length];
        tests = new String[itemArray.length];
        for (int i = 0; i < itemArray.length; ++i) {
            if (itemArray[i].contains("|")) {
                int index = itemArray[i].indexOf('|');
                titleArray[i] = itemArray[i].substring(index + 1);
                tests[i] = itemArray[i].substring(0, index);
            } else {
                titleArray[i] = itemArray[i];
                tests[i] = itemArray[i];
            }
        }
        lv_test.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_expandable_list_item_1, titleArray));

        OnInit();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_clean:
                tv_log.setText("");
                break;
            case R.id.btn_shot:
                Shot(v);
                break;
            default:
                break;
        }
    }

    private void Shot(View v) {
        View rootView = v.getRootView();
        rootView.setDrawingCacheEnabled(true);
        rootView.buildDrawingCache();
        Bitmap bitmap = rootView.getDrawingCache();
        if (bitmap != null) {
            FileOutputStream outStream;
            try {
                SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyyMMddhh");
                String date = sDateFormat.format(new java.util.Date());
                String fileDir = "/sdcard/testframe";
                File file = new File(fileDir);
                if (!file.exists()) {
                    file.mkdirs();
                }

                String fileName = fileDir + "/view_screenshot_";
                fileName += date;
                fileName += ".png";

                file = new File(fileName);
                outStream = new FileOutputStream(file);
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
                outStream.flush();
                outStream.close();

                _appendText("Save To:" + fileName);
            } catch (Exception e) {
                _appendText(e);
            }
        }
    }

    protected SpannableStringBuilder _getColorText(int color, String text) {
        SpannableStringBuilder style = new SpannableStringBuilder(text);
        style.setSpan(new ForegroundColorSpan(color), 0, text.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
        return style;
    }

    protected void _appendText(final String msg, final int color) {
        tv_log.post(new Runnable() {
            @Override
            public void run() {
                Log.i("TEXT", msg);
                tv_log.append(_getColorText(color, msg + "\n"));
                sv_log.fullScroll(ScrollView.FOCUS_DOWN);
            }
        });
    }

    protected void _appendText(final String msg) {
        _appendText(msg, TextNote);
    }

    protected void _appendText(final Throwable e) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);
        String errMsg = sw.toString();

        _appendText(errMsg, TextError);
    }

    protected SpannableStringBuilder _getBitmapText(String path) {
        Bitmap b = BitmapFactory.decodeFile(path);
        ImageSpan imgSpan = new ImageSpan(this, b);
        SpannableStringBuilder spanString = new SpannableStringBuilder("icon");
        spanString.setSpan(imgSpan, 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        return spanString;
    }

    protected void _appendImage(final String path) {
        tv_log.post(new Runnable() {
            @Override
            public void run() {
                tv_log.append(_getBitmapText(path));
                tv_log.append("\n");
                sv_log.fullScroll(ScrollView.FOCUS_DOWN);
            }
        });
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, final int position, long id) {
        long currentTimeMillis = System.currentTimeMillis();
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(currentTimeMillis);
        String sTime = String.format("%02d:%02d:%02d.%03d ", calendar.get(Calendar.HOUR_OF_DAY),
                calendar.get(Calendar.MINUTE), calendar.get(Calendar.SECOND), calendar.get(Calendar.MILLISECOND));

        tv_log.append(_getColorText(TextTips, sTime));
        tv_log.append(_getColorText(TextLogger, "Call:<"));
        tv_log.append(_getColorText(TextInfo, tests[position]));
        tv_log.append(_getColorText(TextLogger, ">\n"));

        Thread workThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    long currentTimeMillis = System.currentTimeMillis();
                    Method method = null;
                    method = mClassModule.getMethod(tests[position]);
                    method.invoke(mObject);

                    long workTimeMs = System.currentTimeMillis() - currentTimeMillis;
                    String sMsg = "耗时:<";
                    sMsg += workTimeMs;
                    sMsg += "ms>";
                    _appendText(sMsg, TextLogger);
                } catch (Exception e) {
                    _appendText(e);
                }
            }
        });
        workThread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                _appendText("Thread UncaughtException:", TextHighWarning);
                _appendText(e);
            }
        });
        workThread.start();
    }

    private static final int REQUEST_EXTERNAL_STORAGE = 1;
    private static String[] PERMISSIONS_STORAGE = {
            "android.permission.READ_EXTERNAL_STORAGE",
            "android.permission.WRITE_EXTERNAL_STORAGE"};

    public void verifyStoragePermissions() {

        try {
            //检测是否有写的权限
            int permission = ActivityCompat.checkSelfPermission(this,
                    "android.permission.WRITE_EXTERNAL_STORAGE");
            if (permission != PackageManager.PERMISSION_GRANTED) {
                // 没有写的权限，去申请写的权限，会弹出对话框
                ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    protected void OnInit() {

        _driver = new HardwareDriver("/sdcard/H003_Cancel");
        _cipher = new CipherDriver();

        _bth = new BluetoothTransmitHandler();
        _com3 = new JniComTransmitHandler("/sdcard/H003_Cancel");
        _hiddev = new HidTransmitHandler(getApplicationContext());
        _hid = new HidFixedTransmitHandler();
        _hid.Select(_hiddev, 1, 1);

        _driver.setHandler(_bth);

        verifyStoragePermissions();
    }

    public JSONObject OnCall(ICommandDriver driver, String cmd, JSONObject arg) {
        _appendText("Command:" + cmd);
        if (arg != null) {
            _appendText(cmd + " Arg:");
            _appendText(arg.toString(), TextMessage);
        }
        JSONObject rlt = new JSONObject();
        boolean bOK = driver.OnCommand(cmd, arg, rlt);
        _appendText("Result:");
        _appendText(cmd + "=" + rlt.toString(), bOK ? TextResult : TextError);
        return rlt;
    }

    public JSONObject OnCall(String cmd, JSONObject arg) {
        return OnCall(_driver, cmd, arg);
    }

    public String getBthAddress(BluetoothAdapter bluetoothAdapter) {
        try {
            Field field = bluetoothAdapter.getClass().getDeclaredField("mService");
            // 参数值为true，禁用访问控制检查
            field.setAccessible(true);
            Object bluetoothManagerService = field.get(bluetoothAdapter);
            if (bluetoothManagerService == null) {
                return null;
            }
            Method method = bluetoothManagerService.getClass().getMethod("getAddress");
            Object address = method.invoke(bluetoothManagerService);
            if (address != null && address instanceof String) {
                return (String) address;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    public void GetBTH() {
        BluetoothAdapter bthAdapter = BluetoothAdapter.getDefaultAdapter();
        if (bthAdapter == null) {
            _appendText("获取本地蓝牙失败(getDefaultAdapter is null)", TextError);
            return;
        }
        String name = bthAdapter.getName();
        String adr = getBthAddress(bthAdapter);

        _appendText("本地蓝牙名称:", TextMessage);
        _appendText(name, TextInfo);

        _appendText("本地蓝牙地址:", TextMessage);
        _appendText(adr, TextInfo);

        JSONArray jsonArray = new JSONArray();
        JSONObject json = new JSONObject();
        try {
            json.put("Name", name);
            json.put("Address", adr);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        jsonArray.put(json);

        String sCfg = jsonArray.toString();
        _appendText(sCfg, TextMessage);

        File file = new File("/sdcard/BTH.json");
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            FileWriter writer = new FileWriter(file, false);
            writer.write(sCfg);
            writer.flush();
            writer.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void BindBTH() {
        GetBTH();

        JSONObject arg = new JSONObject();
        try {
            arg.put("Name", "NT");
        } catch (JSONException e) {
            e.printStackTrace();
        }

        if (!_bth.Open(arg)) {
            _appendText("连接背夹失败,请检查背夹是否已配对", TextError);
            return;
        }
        _driver.setHandler(_bth);

        arg = new JSONObject();
        try {
            arg.put("ArraySize", 8192);
            arg.put("Timeout", 15000);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        _driver.OnCommand("DEV::SetArraySize", arg);
        _driver.OnCommand("DEV::SetTimeoutMS", arg);
        _appendText("连接背夹成功", TextMessage);

        arg = new JSONObject();
        try {
            arg.put("SrcFile", "/sdcard/BTH.json");
            arg.put("DstFile", "/sdcard/H003/White/BTH.json");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        JSONObject rlt = new JSONObject();
        boolean bOK = _driver.OnCommand("PushFile", arg, rlt);
        _bth.Close();

        if (bOK) {
            _appendText("蓝牙绑定成功", TextMessage);
            _appendText(rlt.toString(), TextInfo);
        } else {
            _appendText("蓝牙绑定失败", TextError);
        }
    }

    public void OpenBTH() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("Name", "NT");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        if (_bth.Open(arg)) {
            _driver.setHandler(_bth);
            _basedev = _bth;

            arg = new JSONObject();
            try {
                arg.put("ArraySize", 8192);
                arg.put("Timeout", 15000);
            } catch (JSONException e) {
                e.printStackTrace();
            }
            _driver.OnCommand("DEV::SetArraySize", arg);
            _driver.OnCommand("DEV::SetTimeoutMS", arg);
            _appendText("打开蓝牙成功", TextMessage);
        } else {
            _appendText("打开蓝牙失败", TextHighWarning);
        }
    }

    public void OpenCOM() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("Name", "/dev/ttyS3");
            arg.put("Baud", "115200");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        if (_com3.Open(arg)) {
            _driver.setHandler(_com3);
            _basedev = _com3;
            _appendText("打开串口3成功", TextMessage);
        } else {
            _appendText("打开串口3失败", TextHighWarning);
        }
    }

    public void OpenHID() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("VID", 0x1DFC);
            arg.put("PID", 0x8915);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        if (_hiddev.Open(arg)) {
            _driver.setHandler(_hid);
            _basedev = _hiddev;
            _appendText("打开HID成功", TextMessage);
        } else {
            _appendText("打开HID失败", TextHighWarning);
        }
    }

    public void Close() {
        if (_basedev != null) {
            _basedev.Close();
        }
    }

    public void ScanForCardUID() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("UID", "");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("IC.ScanForCardUID", arg);
    }

    public void PollCard() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("Timeout", 5000);
            arg.put("FLAG", "");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        JSONObject rlt = OnCall("Card.PollCard", arg);
        arg = new JSONObject();
        try {
            arg.put("SLOT", rlt.optString("SLOT_NAME"));
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("IC.ActiveSLOT", arg);
    }

    public void ReadMAG() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("Timeout", 10000);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("MAG.WaitForMagCard", arg);
    }

    public void ReadIDC() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("Timeout", 5000);
            arg.put("FingerFormat", "Hex");
            arg.put("Bmpfile", "/sdcard/zp.bmp");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("ID.WaitIdCard", arg);
    }

    public void PowerOn() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("Timeout", 2000);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("IC.WaitForCard", arg);
    }

    public void Apdu() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("sApdu", "00A4040005A000000333");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("IC.Apdu", arg);
    }

    public void ApplePay() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("Amount", 0);
            arg.put("OtherAmount", 0);
            arg.put("AmountType", 0);
            arg.put("AmountCode", 156);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        JSONObject rlt = OnCall("PBOC.SetAmountTLV", arg);

        arg = new JSONObject();
        try {
            arg.put("TLV", rlt.optString("AmountTLV"));
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("ApplePay.GetCardNumber", arg);
    }

    public void GetCardNumber() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("AID", "");
            arg.put("TAG", "5A");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("PBOC.GetInformation", arg);
        OnCall("PBOC.ParseToINFO", arg);
    }

    public void DevPbocGetInformation() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("SLOT", 0xFF);
            arg.put("TAG", "5A 9F79 57 82 8C 8D");
            arg.put("FLAG", "ABCD");
            arg.put("AID", "A000000333");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("DevPBOC.GetInformation", arg);
        OnCall("PBOC.ParseToINFO", arg);

        arg = new JSONObject();
        try {
            arg.put("Name", "App");
            arg.put("IsExport", true);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("PBOC.SetTLV", arg);
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    public void WltTpBMP() throws JSONException {
        JSONObject arg = new JSONObject();
        try {
            arg.put("WltFormat", "HEX");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        JSONObject rlt = OnCall("ID.WaitIdCard", arg);
        try {
            rlt.put("Bmpfile", "/sdcard/zp.bmp");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("ID.WltToBMP", rlt);

        arg = new JSONObject();
        arg.put("Arg", new JSONObject());
        arg.put("CMD", "StartOpp");
        arg.put("Module", "BluetoothServiceHandler");
        arg.put("Router", "BTH");
        OnCall("APP.OnCall", arg);
        synchronized (_bth) {
            boolean bOK = _bth.PullOppFile("/sdcard/zp.bmp", "/sdcard/zp.bmp");
            if (bOK)
                _appendImage("/sdcard/zp.bmp");
        }
    }

    public void ParseID() throws JSONException {
        JSONObject arg = new JSONObject();
        arg.put("TxtMSG", "5A00480045004E0047004A00490041004E002C00590041004E004700420045004E0020002000200020002000200020002000200020002000200020002000200020002000200020002000200020002000200020002000200020002000200020002000200020002000200020002000200020002000200020003200430041004E00310031003000300038003100300038003000330031003900430041004E00C18BF64E37682C67200020002000200020002000200020002000200020003200300031003500310030003200350032003000320035003100300032003400310039003800310030003800300033003000310031003500300030004900200020002000");
        OnCall("ID.ParseToTXT", arg);
        arg = new JSONObject();
        arg.put("TxtMSG", "D1916B942000200020002000200020002000200020002000200020002000320020002000310039003900340030003800320033001753AC4E025E7F89CE573A530D597451E895165927595788390039003900F753629631003100F7537C69330055534351350030003200A45B20002000200020002000200020003800310030003000300030003100390039003400300038003200330030003000310033001753AC4E025E6C51895B405C7F89CE570652405C200020002000200020003200300031003700310030003200370032003000320032003100300032003600300030003000300030003000300030003000300031002000200020004A00200020002000");
        OnCall("ID.ParseToTXT", arg);
    }

    public void GetPin() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("Timeout", 2000);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("PIN.WaitInput", arg);

        arg = new JSONObject();
        while (true) {
            JSONObject rlt = OnCall("PIN.WaitKey", arg);
            if (rlt.optInt("CODE", 0) != 0) {
                break;
            }
            String key = rlt.optString("Key", "");
            _appendText("Input:" + key);
            if (key.equals("Enter")) {
                break;
            }
        }
        OnCall("PIN.CancelInput", arg);
    }

    public void FingerImage() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("Timeout", 10000);
            arg.put("Bmpfile", "/sdcard/finger.bmp");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("TC.GetImage", arg);
    }

    public void EleSign() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("Bmpfile", "/sdcard/sign.png");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("APP.EleSign", arg);
    }

    public void PushFile() {
        JSONObject arg = new JSONObject();
        try {
            String file = "/sdcard/H003/TTS/sound.mp3";
            arg.put("SrcFile", file);
            arg.put("DstFile", file);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("APP.PushFile", arg);
    }

    public void PullFile() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("SrcFile", "/sdcard/H003/remote_id.jpg");
            arg.put("DstFile", "/sdcard/id.jpg");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("APP.PullFile", arg);
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    public void PushOppFile() throws JSONException {
        _appendText("PushOppFile", TextResult);
        JSONObject arg = new JSONObject();
        arg.put("Arg", new JSONObject());
        arg.put("CMD", "StartOpp");
        arg.put("Module", "BluetoothServiceHandler");
        arg.put("Router", "BTH");
        OnCall("APP.OnCall", arg);
        boolean ret = _bth.PushOppFile("/sdcard/test.mp3", "/sdcard/H003/test.mp3", new IOppBack() {
            @Override
            public boolean OppPosition(long position) {
                _appendText("position=" + position, TextResult);
                return false;
            }
        });
        _appendText("ret=" + ret, ret ? TextResult : TextError);
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    public void PullOppFile() throws JSONException {
        _appendText("PullOppFile", TextResult);
        JSONObject arg = new JSONObject();
        arg.put("Arg", new JSONObject());
        arg.put("CMD", "StartOpp");
        arg.put("Module", "BluetoothServiceHandler");
        arg.put("Router", "BTH");
        OnCall("APP.OnCall", arg);
        boolean ret = _bth.PullOppFile("/sdcard/H003/test.mp3", "/sdcard/test1.mp3", new IOppBack() {
            @Override
            public boolean OppPosition(long position) {
                _appendText("position=" + position, TextResult);
                return false;
            }
        });
        _appendText("ret=" + ret, ret ? TextResult : TextError);
    }

    public void Speak() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("Speak", "sound");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("APP.Speak", arg);
    }

    public void Cancel() {
        JSONObject arg = new JSONObject();
        try {
            arg.put("Timeout", 1000);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OnCall("Unit::Cancel", arg);
    }
}
